﻿using Newtonsoft.Json;
using Onetube.Helpers;
using Onetube.Providers.EventArgsClasses;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking.BackgroundTransfer;
using Windows.Storage;

namespace Onetube.Providers.YoutubeDownloader
{
    public class DownloadItem : INotifyPropertyChanged, IDisposable
    {
        Providers.YoutubeVideoProvider provider = null;
        private CancellationTokenSource cancelToken;
        Providers.SettingsProvider settingsProvider = null;
        private bool isCompleted = false;

        public DownloadItem()
        {
            state = DownloadState.NotStarted;
            provider = new YoutubeVideoProvider();
            settingsProvider = new SettingsProvider();
            cancelToken = new CancellationTokenSource();
        }

        public DownloadType Type { get; set; }

        public Guid ID { get; set; }

        private string videoId;

        public string VideoId
        {
            get
            {
                return videoId;
            }
            set
            {
                videoId = value;
            }
        }

        private Uri uri;

        [JsonIgnore]
        public Uri Uri
        {
            get
            {
                return uri;
            }
        }

        private DownloadState state;

        [JsonIgnore]
        public DownloadState State
        {
            get
            {
                return state;
            }
            set
            {
                state = value;
               
                  NotifyPropertyChanged("State");
            }
        }

        private int progressCount;
     
        public int ProgressCount
        {
            get
            {
                return progressCount;
            }
            set
            {
                progressCount = value;
               NotifyPropertyChanged("ProgressCount");
              
            }
        }

        private Onetube.Entities.VideoQuery.Item videoDetails;
      
        public Onetube.Entities.VideoQuery.Item VideoDetails
        {
            get
            {
                return videoDetails;
            }
            set
            {
                videoDetails = value;
                NotifyPropertyChanged("VideoDetails");
            }
        }

        private string name;

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }
	
        private string mediaSize;

        public string MediaSize
        {
            get
            {
                return mediaSize;
            }
            set
            {
                mediaSize = value;
                 NotifyPropertyChanged("MediaSize");
            }
        }

        public string Extension { get; set; }

        public event EventHandler<DownLoadCompleted> OnDownloadCompleted;
        public event EventHandler<DownloadFailed> OnDownloadFailed;
        public event EventHandler<DownloadStarted> OnDownloadStarted;
        public event EventHandler<DownloadProgressChanged> OnDownloadProgressChanged;
        public event EventHandler<DownloadStarting> OnDownloadStarting;

        public async Task<long> GetFileSize(string videoId, Uri uri)
        {
            TaskCompletionSource<long> taskCompletionSource = new TaskCompletionSource<long>();
            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
                request.AllowReadStreamBuffering = false;
                request.BeginGetResponse(asy =>
                {
                    try
                    {
                        HttpWebRequest request1 = (HttpWebRequest)asy.AsyncState;
                        HttpWebResponse response = (HttpWebResponse)request1.EndGetResponse(asy);
                       
                        taskCompletionSource.SetResult(response.ContentLength);
                        response.Dispose();
                        request.Abort();
                        request1.Abort();
                        request = null;
                        request1 = null;
                    }
                    catch (Exception ex)
                    {
                        taskCompletionSource.SetResult(0);
                    }
                }, request);
            }
            catch (Exception ex)
            {
                taskCompletionSource.TrySetException(ex);
            }
            return await taskCompletionSource.Task;
        }
        
        //TODO Update
        public async void StartDownload(string videoId, Uri uri, DownloadType type, string extension="mp4", bool isStarting=true)
        {
            try
            {
                this.VideoId = videoId;
                state = DownloadState.Starting;
                Extension = extension;
                RaiseDownloadStarting(videoId);
                string title = videoId;
           
                var videoInfo = await provider.GeVideoInfoAsync(videoId, "MX", cancelToken);
                if (videoInfo != null && videoInfo.Items != null)
                {
                    VideoDetails = videoInfo.Items.FirstOrDefault();
                    videoInfo = null;
                }

                if (isStarting)
                {
                    var size = await GetFileSize(videoId, uri);
                    var bytes = ByteSize.FromBytes(size);
                    MediaSize = bytes.ToString("#.## MB");
                }
              
                this.uri = uri;

                //                Name = string.Format("{0}_{1}.{2}", title, type.ToString(), extension);
                var invalidChars = Path.GetInvalidFileNameChars();

                var invalidCharsRemoved = VideoDetails.Snippet
                                                      .Title
                                                      .Where(x => !invalidChars.Contains(x))
                                                      .ToArray();
                Name = string.Format("{0}.{1}", new string(invalidCharsRemoved), extension);
                StorageFile destinationFile = null;
                if (isStarting)
                {
                    var useSDCard = settingsProvider.GetValue<bool>("SDCard");
                    var externalFolder = await GetExternalDevice();
                    if (useSDCard && externalFolder != null)
                    {
                        if (externalFolder != null)
                        {
                            destinationFile = await externalFolder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);
                        }
                    }
                    else
                    {
                        var onetubeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("OnetubePro", CreationCollisionOption.OpenIfExists);
                        var videoFolder = await onetubeFolder.CreateFolderAsync("Videos", CreationCollisionOption.OpenIfExists);
                        var musicFolder = await onetubeFolder.CreateFolderAsync("Music", CreationCollisionOption.OpenIfExists);
                        switch (type)
                        {
                            case DownloadType.Video:
                                destinationFile = await videoFolder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);

                                break;
                            case DownloadType.Audio:
                                destinationFile = await musicFolder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);

                                break;
                            default:
                                break;
                        }
                    }
                }
                
                if (isStarting)
                {
                    DownloadOperation download = DownloaderContext.Downloader.CreateDownload(uri, destinationFile);
                    ID = download.Guid;
                    download.Priority = BackgroundTransferPriority.Default;
                    var progressCalBack = new Progress<DownloadOperation>(DownloadProgress);
                    state = DownloadState.Started;
                    RaiseDownloadStarted();
                    await download.StartAsync().AsTask(cancelToken.Token, progressCalBack);
                }
                else
                {
                    if (!ID.Equals(Guid.Empty))
                    {
                        var backgroundDownloads = await BackgroundDownloader.GetCurrentDownloadsForTransferGroupAsync(BackgroundTransferGroup.CreateGroup("OnetubeApp"));
                        if (backgroundDownloads != null)
                        {
                            var bkdownload = backgroundDownloads.FirstOrDefault(x => x.Guid == ID);
                            if (bkdownload != null)
                            {
                                DetermineCurrentStatus(bkdownload);
                                RaiseDownloadStarted();
                                var progressCalBack = new Progress<DownloadOperation>(DownloadProgress);
                                bkdownload.AttachAsync().AsTask(cancelToken.Token, progressCalBack);
                            }
                        }
                    }
                    else
                    {
                        RaiseDownloadFailed(new Exception("No se encuentra el ID correspondiente"));
                        state = DownloadState.Failed;
                    }
                }
            }
            catch (Exception ex)
            {
                state = DownloadState.Failed;
                RaiseDownloadFailed(ex);
                Debug.WriteLine(ex.Message);
            }
        }

        private void DownloadProgress(DownloadOperation download)
        {
            try
            {
                try
                {
                    GC.Collect();
                }
                catch (Exception ex)
                {
                }

                double percent = 100;
                if (download.Progress.TotalBytesToReceive > 0 &&
                    download.Progress.BytesReceived <= download.Progress.TotalBytesToReceive)
                {
                    percent = (download.Progress.BytesReceived * 100) / download.Progress.TotalBytesToReceive;
                    ProgressCount = System.Convert.ToInt32(percent);
                    RaiseDownloadProgress(System.Convert.ToInt32(percent));
                    Debug.WriteLine(string.Format("Progress:{0}, Guid={1}", percent, download.Guid.ToString()));
                    if (percent >= 100 && isCompleted == false)
                    {
                        isCompleted = true;
                        RaiseDownloadCompleted("sample");
                    }
                }

                if (download.Progress.HasRestarted)
                {
                    Debug.WriteLine("Item has been restarted");
                }
                if (download.Progress.HasResponseChanged)
                {
                    Debug.WriteLine(string.Format("Response updated:{0}", download.GetResponseInformation().StatusCode));
                }
                DetermineCurrentStatus(download);
            }
            catch (Exception ex)
            {
                state = DownloadState.Failed;
                RaiseDownloadFailed(ex);
            }
        }
  
        private void DetermineCurrentStatus(DownloadOperation download)
        {
           switch (download.Progress.Status)
                {
                    case BackgroundTransferStatus.Canceled:
                        State = DownloadState.Cancelded;
                        break;
                    case BackgroundTransferStatus.Completed:
                   
                        State = DownloadState.Completed;
                        break;
                    case BackgroundTransferStatus.Error:
                        State = DownloadState.Failed;
                        RaiseDownloadFailed(new Exception("Error"));
                        break;
                    case BackgroundTransferStatus.Idle:
                        State = DownloadState.Paused;
                        break;
                    case BackgroundTransferStatus.PausedByApplication:
                        State = DownloadState.Paused;
                        break;
                    case BackgroundTransferStatus.PausedCostedNetwork:
                        State = DownloadState.PausedByCostedNetwork;
                        break;
                    case BackgroundTransferStatus.PausedNoNetwork:
                        State = DownloadState.PausedNoNetwork;
                        break;
                    case BackgroundTransferStatus.Running:
                        State = DownloadState.Downloading;
                        Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low, (async () =>
                        {
                            NotifyPropertyChanged("State");
                        }));
                   
                        break;
                    default:
                        State = DownloadState.Failed;
                        break;
                }
        }

        public void CancelDownload()
        {
            if (cancelToken != null)
            {
                try
                {
                    cancelToken.Cancel();
                    state = DownloadState.Cancelded;
                }
                catch (Exception)
                {
                }
            }
        }

        public void ResumeDownload(DownloadOperation download)
        {
            if (download.Guid == this.ID)
            {
                try
                {
                    download.Resume();
                    state = DownloadState.Downloading;
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }
        }

        private void RaiseDownloadStarted()
        {
            Debug.WriteLine("Download started");
            if (OnDownloadStarted != null)
            {
                OnDownloadStarted.Invoke(this, new DownloadStarted(VideoId));
            }
        }

        private void RaiseDownloadCompleted(string path)
        {
            if (OnDownloadCompleted != null)
            {
                OnDownloadCompleted.Invoke(this, new DownLoadCompleted(VideoId, path,Type));
            }
        }

        private void RaiseDownloadStarting(string videoId)
        {
            Debug.WriteLine("Download starting");
            if (OnDownloadStarting != null)
            {
                state = DownloadState.Starting;
                OnDownloadStarting.Invoke(this, new DownloadStarting(VideoId));
            }
        }

        private void RaiseDownloadProgress(int progress)
        {
            if (OnDownloadProgressChanged != null)
            {
                state = DownloadState.Downloading;
                OnDownloadProgressChanged.Invoke(this, new DownloadProgressChanged(VideoId, progress));
            }
        }

        private void RaiseDownloadFailed(Exception ex)
        {
            if (OnDownloadFailed != null)
            {
                state = DownloadState.Failed;
                OnDownloadFailed.Invoke(this, new DownloadFailed(VideoId, ex));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        // This method is called by the Set accessor of each property. 
        // The CallerMemberName attribute that is applied to the optional propertyName 
        // parameter causes the property name of the caller to be substituted as an argument. 
        public async void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                await  Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low, (async () =>
                {
                  PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }));
               
            }
        }

        private async Task<StorageFolder> GetExternalDevice()
        {
            try
            {
                StorageFolder externalDevices = KnownFolders.RemovableDevices;
                var folders = await externalDevices.GetFoldersAsync().AsTask();
                return folders.FirstOrDefault();
            }
            catch (Exception)
            {
            }
            return null;
        }

        public void Dispose()
        {
            if (cancelToken != null)
            {
                cancelToken.Dispose();
                cancelToken = null;
            }
           
            GC.SuppressFinalize(this);
        }
    }
}